package fi.helsinki.cs.scheduler3000;

/**
 * @author Team TA's
 */
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Scanner;

import fi.helsinki.cs.scheduler3000.Weekday.Day;

public class Cli {
    private static final String END_COMMAND = "/q";
    
    private static Scanner scanner = new Scanner(System.in);
    private static Schedule schedule = null;

    public static void main(String[] args) {
        Character command;

        do {
            System.out.println();
            printCommands();
            command = extractCommand(prompt());

            switch (command) {
                case 'p':
                    if (schedule == null) { // cannot do this if schedule is not existing
                        break;
                    }
                    printReportToScreenDialog();
                    break;

                case 'a':
                    if (schedule == null) { // cannot do this if schedule is not existing
                        break;
                    }
                    newEventDialog();
                    break;

                case 's':
                    if (schedule == null) { // cannot do this if schedule is not existing
                        break;
                    }
                    saveScheduleDialog();
                    break;

                case 'f':
                    if (schedule == null) { // cannot do this if schedule is not existing
                        break;
                    }
                    printReportToFileDialog();
                    break;

                case 'n':
                    newScheduleDialog();
                    break;

                case 'o':
                    openScheduleDialog();
                    break;

                case 'q':
                    System.exit(0);
                    break;

                default:
                    System.out.println("Invalid command");
                    break;
            }
        } while (true);
    }

    /**
     * Tarkistaa, että syötetty merkkijono on luku välillä [1,7].
     * Tulostaa myös virheilmoituksen virheen sattuessa.
     * @param in Tarkistettava merkkijono
     * @return boolean True, jos luku välillä [1,7], muuten false.
     */
    // TODO: Move to other class??
    private static boolean validateDayNumber(String in) {
        try {
            int dayNumber = Integer.parseInt(in);

            if (dayNumber >= 1 && dayNumber <= 7) {
                return true;
            }

            System.out.println("Day number must be between 1 and 7");
        } catch (NumberFormatException e) {
            System.out.println("\"" + in + "\" is not a valid number");
        }

        return false;
    }

    // TODO: Move to other class??
    private static boolean validateDayNumber(String in, Schedule schedule) {
        // check if the date is valid at all
        if (!validateDayNumber(in)) {
            return false;
        }
        // check if specified date is in the schedule
        return schedule.getSchedule().containsKey(Weekday.intToEnumMap.get(Integer.parseInt(in)));
    }

    // TODO: Move to other class??
    private static HashMap<String, Object> getOptions(String key, Day value) {
        HashMap<String, Object> ret = new HashMap<String, Object>();
        ret.put(key, value);
        return ret;
    }

    // TODO: Move to other class??
    private static HashMap<String, Object> getOptions(String key, ArrayList<Day> value) {
        HashMap<String, Object> ret = new HashMap<String, Object>();
        ret.put(key, value);
        return ret;
    }

    private static void newEventDialog() {
        String startTime = null,
                endTime = null,
                location = null,
                title = null,
                eventDayTemp;
        Event event = null;
        Day eventDay = null;

        do {
            System.out.println("");

            System.out.println("Enter the day for the event.");
            printDays();
            eventDayTemp = prompt();

            if (eventDayTemp.equals(END_COMMAND)) {
                return;
            }

            System.out.println("What is the start time?");
            startTime = prompt();

            System.out.println("What is the end time?");
            endTime = prompt();

            System.out.println("What should this event be named as?");
            System.out.println("(press ENTER to skip)");
            title = prompt();

            System.out.println("Where is this event held?");
            System.out.println("(press ENTER to skip)");
            location = prompt();

            try {
                if (!validateDayNumber(eventDayTemp)) {
                    continue;
                }

                eventDay = Weekday.intToEnumMap.get(Integer.parseInt(eventDayTemp));
                event = new Event(startTime, endTime, title, location);
                break; // success, get out of the do-while

            } catch (IllegalArgumentException e) {

                System.out.println("Sorry, but some mistakes were made:");
                System.out.println(e.getMessage());

            }
        } while (true);

        System.out.print("Adding event to schedule...");

        try {
            schedule.addEvent(eventDay, event);
        } catch (IllegalArgumentException e) {
            System.out.println("Something went wrong:");
            System.out.println(e.getMessage());
            System.out.println("Sorry, but once more");
            newEventDialog();
            return; // this is for when newEventDialog finally succeedes, we don't print out the last ok!'s
        }

        System.out.println("ok!");

    }

    private static void newScheduleDialog() {
        String in = null;
        HashSet<Integer> dates = new HashSet<Integer>();

        System.out.println("Enter the period this schedule is for:");
        String period = prompt();

        System.out.println("Give days you want to include in the schedule.");
        System.out.println("Stop giving the dates by entering \"" + END_COMMAND + "\".");
        System.out.println("One at a time, please.");

        do {
            printDays();
            printSelection(dates);
            in = prompt().trim();

            if (in.toLowerCase().equals(END_COMMAND)) {
                break;
            } else {
                if (validateDayNumber(in.trim())) {
                    dates.add(Integer.parseInt(in.trim()));
                }
            }

        } while (true);

        System.out.print("Creating schedule...");

        ArrayList<Day> days = new ArrayList<Day>();
        for (Integer d : dates) {
            days.add(Weekday.intToEnumMap.get(d));
        }

        schedule = new Schedule(days, period);

        System.out.println("ok!");

    }

    private static boolean openSchedule(String filename) {
        try {
            schedule.openSchedule(filename);
            return true;
        } catch (NullPointerException e) {
            System.out.println("Invalid file name");
        } catch (IOException e) {
            System.out.println("Cannot read \"" + filename + "\" from ObjectInputStream");
        } catch (ClassNotFoundException e) {
            System.out.println("Cannot find class for the object when reading \"" + filename + "\"");
        }

        return false;
    }

    private static void openScheduleDialog() {
        System.out.println("Give name of the file to be opened");
        String filename = prompt().trim();
        while (true) {

            if (!filename.endsWith(".dat")) {
                filename += ".dat";
            }

            if (openSchedule(filename)) {
                break;
            } else {
                System.out.println("Please enter the name of the file again");
                System.out.println("You can exit with " + END_COMMAND);

                filename = prompt().trim();
                if (filename.equals(END_COMMAND)) {
                    return;
                }

            }
        }
    }

    private static void printCommands() {
        System.out.println("Commands");
        System.out.println("--------");
        System.out.println("[N]ew schedule");
        System.out.println("[O]pen schedule from file");
        if (schedule != null) {
            System.out.println("[A]dd event to schedule");
            System.out.println("[S]ave schedule to file");
            System.out.println("[P]rint a report on screen");
            System.out.println("Print a report to [F]ile");
        }
        System.out.println("[Q]uit");
    }

    private static void printDays() {
        printDays(Day.values());
    }

    private static void printDays(Schedule schedule) {
        Day[] days = new Day[schedule.getSchedule().keySet().size()];
        days = schedule.getSchedule().keySet().toArray(days);
        printDays(days);
    }

    private static void printDays(Day[] days) {
        System.out.print("Days are: ");
        for (Day d : days) {
            System.out.print(Weekday.enumToIntMap.get(d));
            System.out.print(" - ");
            System.out.print(Weekday.longNameMap.get(d));
            System.out.print(" ");
        }
        System.out.println();
    }

    private static String prompt() {
        System.out.print(">> ");
        return scanner.nextLine();
    }

    private static Report makeReportDialog() {
        Character command = null;

        while (true) {
            System.out.print("Which type of report do you want to print? Options are: ");
            for (ReportFactory.ReportType type : ReportFactory.ReportType.values()) {
                // make types the way every other option is shown to user
                System.out.print("[" + type.toString().charAt(0) + "]" + type.toString().substring(1).toLowerCase());
                System.out.print(" ");
            }
            System.out.println("[N]one");
            command = extractCommand(prompt());
            String in = null;

            switch (command) {
                case 'd':
                    System.out.println("Which day you want to see your schedule for?");
                    printDays();
                    in = prompt();
                    if (!validateDayNumber(in)) {
                        System.out.println("Invalid date");
                        break;
                    }

                    Day day = Weekday.intToEnumMap.get(Integer.parseInt(in));
                    return ReportFactory.makeReport(ReportFactory.ReportType.DAY, schedule, getOptions("day", day));

                case 'f':
                    return ReportFactory.makeReport(ReportFactory.ReportType.FULL, schedule, null); // full report doesen't need options

                case 'w':
                    ArrayList<Day> days = new ArrayList<Day>();

                    System.out.println("Which days you want to include in this report? You can end with \"" + END_COMMAND + "\"");
                    System.out.println("One at the time, please");

                    while (true) {
                        // print only available dates
                        printDays(schedule);
                        in = prompt();
                        if (in.equals(END_COMMAND)) {
                            break;
                        } else if (!validateDayNumber(in, schedule)) {
                            System.out.println("Unvalid date");
                        } else {
                            days.add(Weekday.intToEnumMap.get(Integer.parseInt(in)));
                        }
                    }

                    return ReportFactory.makeReport(ReportFactory.ReportType.WEEK, schedule, getOptions("days", days));


                case 'n':
                    System.out.println("Returning back to main menu");
                    return null;

                default:
                    System.out.println("Cannot parse " + command);
                    break;
            }
        }
    }

    /**
     * Kysyy käyttäjältä tiedostopolkua ja tulostaa Report-olion sinne.
     */
    private static void printReportToFileDialog() {
        Report report = makeReportDialog();
        if (report != null) {
            System.out.println("Give full file name and path (if applicable)");

            String filename = null;
            while (true) {
                try {
                    filename = prompt().trim();
                    report.printToFile(filename);
                    break;
                } catch (FileNotFoundException e) {
                    System.out.println("File " + filename + " was not found");
                }
            }

            System.out.println("ok!");
        }
    }

    private static void printReportToScreenDialog() {
        Report report = makeReportDialog();
        if (report != null) {
            System.out.println(report);
        }
    }

    /**
     * Tulostaa dates parametrin sisältämät viikonpäivät.
     * @param dates Käyttäjän valitsemat viikonpäivät.
     */
    private static void printSelection(HashSet<Integer> dates) {
        if (dates.size() > 0) {
            System.out.print("You have selected: ");
            for (Integer d : dates) {
                System.out.print(Weekday.longNameMap.get(Weekday.intToEnumMap.get(d)));
                System.out.print(" ");
            }
            System.out.println();
        }
    }

    private static Character extractCommand(String rawInput) {
        return new Character(rawInput.toLowerCase().charAt(0));
    }

    private static boolean saveSchedule(String filename) {
        try {
            schedule.saveSchedule(filename);
        } catch (NullPointerException e) {
            System.out.println("Invalid file name");
        } catch (FileNotFoundException e) {
            System.out.println("Cannot open \"" + filename + "\", something's wrong with it");
            return false;
        } catch (IOException e) {
            System.out.println("Cannot write to \"" + filename + "\"");
            return false;
        }

        return true;
    }

    private static void saveScheduleDialog() {
        System.out.println("Give name of the file to save to (.dat extension is assumed)");
        String filename = prompt().trim();
        if (!filename.endsWith(".dat")) {
            filename += ".dat";
        }
        while (true) {
            if (saveSchedule(filename)) {
                break;
            } else {
                System.out.println("Please enter the name of the file again");
                System.out.println("You can exit with " + END_COMMAND);
                filename = prompt().trim() + ".dat";

                if (filename.trim().toLowerCase().equals(END_COMMAND)) {
                    return;
                }
            }
        }
        System.out.println("Schedule saved as \"" + filename + "\"");
    }
}
